/*
 * Copyright (c) 2012, Marcin Czekaj
 * All rights reserved.
 *
 * This software is open-source under the BSD license; see LICENSE.TXT for a description.
 */
package pl.edu.uj.fais.binding.plugin;

import org.apache.commons.collections15.Factory;

import pl.edu.uj.fais.binding.plugin.EditingCheckingGraphMousePlugin.EdgeChecker;
import pl.edu.uj.fais.binding.plugin.EditingCheckingGraphMousePlugin.VertexChecker;
import pl.edu.uj.fais.controller.Controller;
import pl.edu.uj.fais.graph.elements.Link;
import pl.edu.uj.fais.graph.elements.Node;
import pl.edu.uj.fais.graph.elements.ProxyNode;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.EdgeType;
import edu.uci.ics.jung.visualization.VisualizationViewer;

public class ConstrainedEditingPlugin extends
    EditingCheckingGraphMousePlugin<Node, Link> implements
    VertexChecker<Node, Link>, EdgeChecker<Node, Link> {
    
    private Controller controller = Controller.getInstance();
    private boolean isConstrained;
    
    public ConstrainedEditingPlugin() {
        super(getNodeFactory(), getLinkFactory());
        setConstrained(true);
        setVertexChecker(this);
        setEdgeChecker(this);
    }
    
    public void setConstrained(boolean enabled) {
        isConstrained = enabled;
    }
    
    private static Factory<Node> getNodeFactory() {
        return new Factory<Node>() {
            
            @Override
            public Node create() {
                return new Node();
            }
        };
    }
    
    private static Factory<Link> getLinkFactory() {
        return new Factory<Link>() {
            
            @Override
            public Link create() {
                return new Link();
            }
        };
    }
    
    @Override
    public boolean checkVertex(Graph<Node, Link> g,
                               VisualizationViewer<Node, Link> vv,
                               Node vertex) {
        controller.setChanged();
        return true;
    }
    
    @Override
    public boolean checkEdge(Graph<Node, Link> graph,
                             VisualizationViewer<Node, Link> viewer,
                             Link edge,
                             Node start,
                             Node end,
                             EdgeType edgeType) {
        
        if (!isConstrained) {
            controller.setChanged();
            return true;
        }
        
        if (isSelfLoop(start, end)) {
            return false;
        }
        
        if (isConnectingProxies(start, end)) {
            return false;
        }
        
        if (start.isProxyNode() ^ end.isProxyNode()) {
            
            if (isNodeAndProxyConnected(graph, start, end)) {
                return false;
            }
            
            // allow to create a new link
            addLinkToExistingProxy(start, end, edge);
            controller.setChanged();
            
            return true;
        }
        
        // from now on only (node <-> node) connections are left
        makeHyperEdge(graph, start, end);
        controller.setChanged();
        
        return false;
    }
    
    private boolean isSelfLoop(Node start, Node end) {
        return start.getId() == end.getId();
    }
    
    private boolean isConnectingProxies(Node start, Node end) {
        return start.isProxyNode() && end.isProxyNode();
    }
    
    private boolean isNodeAndProxyConnected(Graph<Node, Link> graph,
                                            Node start,
                                            Node end) {
        return graph.findEdge(start, end) != null;
    }
    
    private void addLinkToExistingProxy(Node start, Node end, Link edge) {
        ProxyNode proxy = (ProxyNode) ((start.isProxyNode()) ? start : end);
        edge.setProxy(proxy);
    }
    
    private void makeHyperEdge(Graph<Node, Link> graph, Node start, Node end) {
        
        ProxyNode proxy = new ProxyNode();
        
        graph.addVertex(proxy);
        
        graph.addEdge(new Link(proxy), start, proxy);
        graph.addEdge(new Link(proxy), end, proxy);
        
        controller.centerProxyNode(proxy, start, end);
    }
    
}
